/*
    基于future和packaged_task实现一个线程池
    线程池设计思想: 用户传入需要执行的函数以及参数，由线程池中的工作线程执行函数来完成任务
    线程池的实现:
    成员:
        1. 任务队列: 存放用户传入的任务
        2. 互斥锁 & 条件变量: 实现多线程间的同步与互斥，从而保证任务队列的安全
        3. 固定数量的线程：工作线程，从任务队列中获取任务并执行
        4. 结束标志: 用于停止线程池的工作
    操作:
        1. 线程池入口函数:
        2. 入队任务: 将用户传入的函数以及参数构建成任务对象，放入到任务队列中
        3. 停止任务: 停止线程池的工作
*/
#pragma once
#include <iostream>
#include <thread>
#include <future>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <vector>
#include <functional>

namespace mq
{
    class ThreadPool
    {
    public:
        using ThreadPoolPtr = std::shared_ptr<ThreadPool>;
        using Task = std::function<void()>;

    public:
        ThreadPool(int thread_num = std::thread::hardware_concurrency()) : _stop(false)
        {
            for (int i = 0; i < thread_num; ++i)
            {
                _threads.emplace_back(&ThreadPool::entry, this);
            }
        }

        ~ThreadPool()
        {
            stop();
        }

        // 停止任务
        void stop()
        {
            if (_stop == true)
                return;
            _stop = true;
            _cond_var.notify_all();
            for (auto &t : _threads)
            {
                if (t.joinable())
                    t.join();
            }
        }

        // 入队任务
        // 返回值说明: 需要给用户返回一个能够获取结果的对象，显然future很适合，但是future是一个模板，用户传入的函数返回值是不确定的，所以future无法直接确定
        //            可以使用auto作为push返回值，但是需要使用decltype()来进行推导返回值类型，其中fn(args...)可以理解为执行函数获取返回值
        template <typename Fn, typename... Args>
        auto push(Fn &&fn, Args &&...args) -> std::future<decltype(fn(args...))>
        {
            // 1. 将传入的函数打包成一个packaged_task对象
            using return_type = decltype(fn(args...));                                                          // 推导返回值类型
            std::function<return_type()> tmp_fn = std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...); // 将函数和其参数进行绑定，方便后续任务的执行
            std::shared_ptr<std::packaged_task<return_type()>> task = std::make_shared<std::packaged_task<return_type()>>(std::move(tmp_fn));
            std::future<return_type> future = task->get_future();
            // 2. 构造lambda表达式，在表达式内执行任务
            {
                std::unique_lock<std::mutex> lock(_mutex);
                _tasks.emplace_back([task]()
                                    { (*task)(); }); // 将任务打包成lambda表达式入任务队列
                _cond_var.notify_one();
            }
            return future;
        }

    private:
        // 线程的入口函数
        void entry()
        {
            while (!_stop)
            {
                std::vector<Task> tmp_tasks;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    _cond_var.wait(lock, [this]() { 
                        return _stop || !_tasks.empty(); 
                    });
                    _tasks.swap(tmp_tasks);
                }
                for (auto &task : tmp_tasks)
                    task();
            }
        }

    private:
        std::atomic<bool> _stop;           // 停止标志
        std::vector<std::thread> _threads; // 工作线程
        std::vector<Task> _tasks;          // 任务队列
        std::mutex _mutex;
        std::condition_variable _cond_var;
    };
}